﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using DomainDriver.DomainModeling.DataProviders;
using DomainDriver.DomainModeling.DomainObjects;
using DomainDriver.DomainModeling.Queries;
using DomainDriver.DomainModeling.StorageManagers.EntityFrameworkStorage;

namespace DomainDriver.DomainModeling.StorageManagers
{
    public abstract class QueryableStorageManagerBase<KEY, KEYED_DOMAIN_OBJECT> : IStorageManager<KEY, KEYED_DOMAIN_OBJECT>
        where KEYED_DOMAIN_OBJECT : class, IKeyedDomainObject<KEY, KEYED_DOMAIN_OBJECT>
    {
        private IDataProvider m_DataProvider;

        public QueryableStorageManagerBase(IDataProvider dataProvider)
        {
            m_DataProvider = dataProvider;
        }

        public IDataProvider DataProvider
        {
            get { return m_DataProvider; }
        }

        public virtual int Count()
        {
            IEnumerable<KEYED_DOMAIN_OBJECT> dataStore = GetQueryableDataStore();
            int count = dataStore.Count<KEYED_DOMAIN_OBJECT>();
            return count;
        }

        public virtual bool Exists(KEY key)
        {
            List<KEY> keys = new List<KEY>();
            keys.Add(key);

            var keyMatcher = GetKeyMatchingPredicate(keys);
            var dataStore = GetQueryableDataStore();
            var exists = keyMatcher.DoWhere(dataStore).Any();
            return exists;
        }

        public virtual bool Exists(IEnumerable<KEY> keys)
        {
            foreach (KEY key in keys)
            {
                if (!Exists(key))
                { return false; }
            }
            return true;
        }

        public ICollection<KEY> GetKeys()
        {
            Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = ((KEYED_DOMAIN_OBJECT domainObject) => true);
            var keyMatcher = new EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT>(queryPredicate);
            var keys = ReadKeys(keyMatcher);
            return keys;
        }

        public ICollection<KEY> GetKeys(IQuery<KEYED_DOMAIN_OBJECT> query)
        {
            ICollection<KEY> keys = null;
            if (this.Supports_DbQueries() && query.Supports_UnCompiled_QueryPredicate)
            {
                Expression<Func<KEYED_DOMAIN_OBJECT, bool>> queryPredicate = query.GetQueryPredicate_UnCompiled(DataProvider);
                var keyMatcher = new EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT>(queryPredicate);
                keys = ReadKeys(keyMatcher);
            }
            else
            {
                Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = query.GetQueryPredicate(DataProvider);
                var keyMatcher = new EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT>(queryPredicate);
                keys = ReadKeys(keyMatcher);
            }
            return keys;
        }

        public ICollection<DESC> GetDescriptors<DESC>()
            where DESC : IKeyedDomainObjectDescriptor<KEY, KEYED_DOMAIN_OBJECT>, new()
        {
            Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = ((KEYED_DOMAIN_OBJECT domainObject) => true);
            var keyMatcher = new EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT>(queryPredicate);
            var descriptors = ReadDescriptors<DESC>(keyMatcher);
            return descriptors;
        }

        public ICollection<DESC> GetDescriptors<DESC>(IQuery<KEYED_DOMAIN_OBJECT> query)
            where DESC : IKeyedDomainObjectDescriptor<KEY, KEYED_DOMAIN_OBJECT>, new()
        {
            ICollection<DESC> descriptors = null;
            if (this.Supports_DbQueries() && query.Supports_UnCompiled_QueryPredicate)
            {
                Expression<Func<KEYED_DOMAIN_OBJECT, bool>> queryPredicate = query.GetQueryPredicate_UnCompiled(DataProvider);
                var keyMatcher = new EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT>(queryPredicate);
                descriptors = ReadDescriptors<DESC>(keyMatcher);
            }
            else
            {
                Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = query.GetQueryPredicate(DataProvider);
                var keyMatcher = new EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT>(queryPredicate);
                descriptors = ReadDescriptors<DESC>(keyMatcher);
            }
            return descriptors;
        }

        public ICollection<KEYED_DOMAIN_OBJECT> Read()
        {
            Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = ((KEYED_DOMAIN_OBJECT domainObject) => true);

            var keyMatcher = new EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT>(queryPredicate);
            var storedObjects = ReadStoredObjects(keyMatcher);
            var copiedObjects = new List<KEYED_DOMAIN_OBJECT>();

            foreach (KEYED_DOMAIN_OBJECT storedObject in storedObjects)
            {
                copiedObjects.Add(storedObject.Copy());
            }
            return copiedObjects;
        }

        public ICollection<KEYED_DOMAIN_OBJECT> Read(IQuery<KEYED_DOMAIN_OBJECT> query)
        {
            ICollection<KEYED_DOMAIN_OBJECT> storedObjects = null;
            if (this.Supports_DbQueries() && query.Supports_UnCompiled_QueryPredicate)
            {
                Expression<Func<KEYED_DOMAIN_OBJECT, bool>> queryPredicate = query.GetQueryPredicate_UnCompiled(DataProvider);
                var keyMatcher = new EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT>(queryPredicate);
                storedObjects = ReadStoredObjects(keyMatcher);
            }
            else
            {
                Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = query.GetQueryPredicate(DataProvider);
                var keyMatcher = new EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT>(queryPredicate);
                storedObjects = ReadStoredObjects(keyMatcher);
            }

            List<KEYED_DOMAIN_OBJECT> copiedObjects = new List<KEYED_DOMAIN_OBJECT>();
            foreach (KEYED_DOMAIN_OBJECT storedObject in storedObjects)
            {
                copiedObjects.Add(storedObject.Copy());
            }
            return copiedObjects;
        }

        public ICollection<KEYED_DOMAIN_OBJECT> Read(IEnumerable<KEY> keys)
        {
            var keyMatcher = GetKeyMatchingPredicate(keys);
            var storedObjects = ReadStoredObjects(keyMatcher);
            var copiedObjects = new List<KEYED_DOMAIN_OBJECT>();

            foreach (KEYED_DOMAIN_OBJECT storedObject in storedObjects)
            {
                copiedObjects.Add(storedObject.Copy());
            }
            return copiedObjects;
        }

        public KEYED_DOMAIN_OBJECT Read(KEY key)
        {
            var keys = new List<KEY>();
            keys.Add(key);

            var keyMatcher = GetKeyMatchingPredicate(keys);
            var storedObjects = ReadStoredObjects(keyMatcher);

            if (storedObjects.Count < 1)
            { return default(KEYED_DOMAIN_OBJECT); }

            var storedObject = storedObjects.First<KEYED_DOMAIN_OBJECT>();
            var copiedObject = storedObject.Copy();
            return copiedObject;
        }

        public void Add(IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects)
        {
            WriteObjects(domainObjects, ChangeActionType.Create);
        }

        public void Remove(IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects)
        {
            WriteObjects(domainObjects, ChangeActionType.Delete);
        }

        public void Update(IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects)
        {
            WriteObjects(domainObjects, ChangeActionType.Update);
        }

        protected abstract IQueryable<KEYED_DOMAIN_OBJECT> GetQueryableDataStore();
        protected abstract EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT> GetKeyMatchingPredicate(IEnumerable<KEY> keys);
        protected abstract ICollection<KEY> ReadKeys(EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT> keyMatcher);
        protected abstract ICollection<DESC> ReadDescriptors<DESC>(EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT> keyMatcher) where DESC : IKeyedDomainObjectDescriptor<KEY, KEYED_DOMAIN_OBJECT>, new();
        protected abstract ICollection<KEYED_DOMAIN_OBJECT> ReadStoredObjects(EntityKeyMatcher<KEY, KEYED_DOMAIN_OBJECT> keyMatcher);
        protected abstract void WriteObjects(IEnumerable<KEYED_DOMAIN_OBJECT> domainObjects, ChangeActionType changeAction);
    }
}